// Copyright (C) 2008-2012 NVIDIA Corporation.
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#ifndef QSSG_RENDER_COMMANDS_H
#define QSSG_RENDER_COMMANDS_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QtQuick3DUtils/private/qssgrenderbasetypes_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h>

#include <QDebug>
#include <QVariant>

QT_BEGIN_NAMESPACE

struct QSSGRenderEffect;

enum class CommandType
{
    Unknown = 0,
    AllocateBuffer,
    AllocateTexture,
    BindTarget,
    BindBuffer,
    BindShader,
    ApplyInstanceValue,
    ApplyBufferValue,
    Render,
    ApplyValue,
    RenderablesFilter,
    PipelineStateOverride,
    ColorAttachment,
    DepthStencilAttachment,
    DepthTextureAttachment,
    AddShaderDefine,
};

struct QSSGCommand
{
    CommandType m_type;
    QSSGCommand(CommandType inType) : m_type(inType) {}
    const char *typeAsString() const;
    QString debugString() const;
    void addDebug(QDebug &stream) const;
};

enum class AllocateBufferFlagValues
{
    None = 0,
    SceneLifetime = 1,
};

struct QSSGAllocateBufferFlags : public QFlags<AllocateBufferFlagValues>
{
    QSSGAllocateBufferFlags(quint32 inValues) : QFlags(inValues) {}
    QSSGAllocateBufferFlags() {}
    void setSceneLifetime(bool inValue) { setFlag(AllocateBufferFlagValues::SceneLifetime, inValue); }
    // If isSceneLifetime is unset the buffer is assumed to be frame lifetime and will be
    // released after this render operation.
    bool isSceneLifetime() const { return this->operator&(AllocateBufferFlagValues::SceneLifetime); }
};

struct QSSGAllocateBuffer : public QSSGCommand
{
    QByteArray m_name;
    QSSGRenderTextureFormat m_format = QSSGRenderTextureFormat::RGBA8;
    QSSGRenderTextureFilterOp m_filterOp = QSSGRenderTextureFilterOp::Linear;
    QSSGRenderTextureCoordOp m_texCoordOp = QSSGRenderTextureCoordOp::ClampToEdge;
    float m_sizeMultiplier = 1.0f;
    QSSGAllocateBufferFlags m_bufferFlags;
    QSSGAllocateBuffer() : QSSGCommand(CommandType::AllocateBuffer) {}
    QSSGAllocateBuffer(const QByteArray &inName,
                         QSSGRenderTextureFormat inFormat,
                         QSSGRenderTextureFilterOp inFilterOp,
                         QSSGRenderTextureCoordOp inCoordOp,
                         float inMultiplier,
                         QSSGAllocateBufferFlags inFlags)
        : QSSGCommand(CommandType::AllocateBuffer)
        , m_name(inName)
        , m_format(inFormat)
        , m_filterOp(inFilterOp)
        , m_texCoordOp(inCoordOp)
        , m_sizeMultiplier(inMultiplier)
        , m_bufferFlags(inFlags)
    {
    }
    QSSGAllocateBuffer(const QSSGAllocateBuffer &command)
        : QSSGCommand(CommandType::AllocateBuffer),
          m_name(command.m_name),
          m_format(command.m_format),
          m_filterOp(command.m_filterOp),
          m_texCoordOp(command.m_texCoordOp),
          m_sizeMultiplier(command.m_sizeMultiplier),
          m_bufferFlags(command.m_bufferFlags)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "name:" <<  m_name << "format:" << m_format.toString() << "size multiplier:" << m_sizeMultiplier << "filter:" << QSSGBaseTypeHelpers::toString(m_filterOp) << "tiling:" << QSSGBaseTypeHelpers::toString(m_texCoordOp) << "sceneLifetime:" << m_bufferFlags.isSceneLifetime();
    }
};

struct QSSGBindTarget : public QSSGCommand
{
    QSSGRenderTextureFormat m_outputFormat;

    explicit QSSGBindTarget(QSSGRenderTextureFormat inFormat = QSSGRenderTextureFormat::RGBA8)
        : QSSGCommand(CommandType::BindTarget), m_outputFormat(inFormat)
    {
    }
    QSSGBindTarget(const QSSGBindTarget &command)
        : QSSGCommand(CommandType::BindTarget),
          m_outputFormat(command.m_outputFormat)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "format" <<  m_outputFormat.toString();
    }
};

struct QSSGBindBuffer : public QSSGCommand
{
    QByteArray m_bufferName;
    QSSGBindBuffer(const QByteArray &inBufName)
        : QSSGCommand(CommandType::BindBuffer), m_bufferName(inBufName)
    {
    }
    QSSGBindBuffer(const QSSGBindBuffer &command)
        : QSSGCommand(CommandType::BindBuffer),
          m_bufferName(command.m_bufferName)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "name:" <<  m_bufferName;
    }
};

struct QSSGBindShader : public QSSGCommand
{
    QByteArray m_shaderPathKey; // something like "prefix>vertex_filename>fragment_filename:source_sha:y_up_in_fbo[:tonemapping]"
    QSSGBindShader(const QByteArray &inShaderPathKey)
        : QSSGCommand(CommandType::BindShader),
          m_shaderPathKey(inShaderPathKey)
    {
    }
    QSSGBindShader() : QSSGCommand(CommandType::BindShader) {}
    QSSGBindShader(const QSSGBindShader &command)
        : QSSGCommand(CommandType::BindShader),
          m_shaderPathKey(command.m_shaderPathKey)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "key:" <<  m_shaderPathKey << "effect:";
    }
};

// The value sits immediately after the 'this' object
// in memory.
// If propertyName is not valid then we attempt to apply all of the effect property values
// to the shader, ignoring ones that don't match up.
struct QSSGApplyInstanceValue : public QSSGCommand
{
    // Name of value to apply in shader
    QByteArray m_propertyName;
    // type of value
    QSSGRenderShaderValue::Type m_valueType;
    // offset in the effect data section of value.
    quint32 m_valueOffset;
    QSSGApplyInstanceValue(const QByteArray &inName, QSSGRenderShaderValue::Type inValueType, quint32 inValueOffset)
        : QSSGCommand(CommandType::ApplyInstanceValue), m_propertyName(inName), m_valueType(inValueType), m_valueOffset(inValueOffset)
    {
    }
    // Default will attempt to apply all effect values to the currently bound shader
    QSSGApplyInstanceValue()
        : QSSGCommand(CommandType::ApplyInstanceValue), m_valueType(QSSGRenderShaderValue::Unknown), m_valueOffset(0)
    {
    }
    QSSGApplyInstanceValue(const QSSGApplyInstanceValue &command)
        : QSSGCommand(CommandType::ApplyInstanceValue),
          m_propertyName(command.m_propertyName),
          m_valueType(command.m_valueType),
          m_valueOffset(command.m_valueOffset)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "name:" <<  m_propertyName << "type:" << int(m_valueType) << "offset:" << m_valueOffset ;
    }
};

struct QSSGApplyValue : public QSSGCommand
{
    QByteArray m_propertyName;
    QVariant m_value;
    explicit QSSGApplyValue(const QByteArray &inName)
        : QSSGCommand(CommandType::ApplyValue), m_propertyName(inName)
    {
    }
    QSSGApplyValue(const QSSGApplyValue &command)
        : QSSGCommand(CommandType::ApplyValue),
          m_propertyName(command.m_propertyName),
          m_value(command.m_value)
    {
    }
    // Default will attempt to apply all effect values to the currently bound shader
    QSSGApplyValue() : QSSGCommand(CommandType::ApplyValue) {}
    void addDebug(QDebug &stream) const {
        stream << "name:" <<  m_propertyName << "value:" << m_value;
    }
};

struct QSSGApplyBufferValue : public QSSGCommand
{
    QByteArray m_bufferName;
    QByteArray m_samplerName;

    QSSGApplyBufferValue(const QByteArray &bufferName, const QByteArray &shaderSampler)
        : QSSGCommand(CommandType::ApplyBufferValue), m_bufferName(bufferName), m_samplerName(shaderSampler)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "name:" <<  m_bufferName << "sampler:" << m_samplerName;
    }
    QSSGApplyBufferValue(const QSSGApplyBufferValue &command)
        : QSSGCommand(CommandType::ApplyBufferValue),
          m_bufferName(command.m_bufferName),
          m_samplerName(command.m_samplerName)
    {
    }
};

struct QSSGRender : public QSSGCommand
{
    explicit QSSGRender() : QSSGCommand(CommandType::Render) { }

    QSSGRender(const QSSGRender &)
        : QSSGCommand(CommandType::Render)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "(no parameters)";
    }
};

class QSSGRenderablesFilterCommand : public QSSGCommand
{
public:
    enum class RenderableType : quint32 {
        Opaque = 0x1,
        Transparent = 0x2,
        Item2D = 0x4,
    };

    using RenderableTypeT = std::underlying_type<RenderableType>::type;
    static constexpr RenderableTypeT AllRenderableTypes = std::numeric_limits<RenderableTypeT>::max();

    quint32 layerMask = 0xffffffff;
    RenderableTypeT renderableTypes = AllRenderableTypes;

    QSSGRenderablesFilterCommand()
        : QSSGCommand(CommandType::RenderablesFilter)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "layerMask:" <<  layerMask << "renderableTypes:" << renderableTypes;
    }
};

// This is pretty much the same as QSSGAllocateBuffer but
// we'll keep them separate as we might want to diverge further later.
class QSSGAllocateTexture : public QSSGCommand
{
public:
    QSSGAllocateTexture()
        : QSSGCommand(CommandType::AllocateTexture)
    {
    }
    QSSGAllocateTexture(QSSGRenderTextureFormat inFormat)
        : QSSGCommand(CommandType::AllocateTexture),
          m_format(inFormat)
    {
    }

    const QSSGSharedRhiTextureWrapperPtr &texture() const { return m_tex; }
    void setTexture(QSSGSharedRhiTextureWrapperPtr tex) { m_tex = tex; }

    QSSGRenderTextureFormat format() const
    {
        return m_format;
    }

    void setFormat(QSSGRenderTextureFormat format)
    {
        m_format = format;
    }

    void addDebug(QDebug &stream) const
    {
        stream << "format:" << m_format.toString();
    }

private:
    Q_DISABLE_COPY_MOVE(QSSGAllocateTexture)

    QSSGSharedRhiTextureWrapperPtr m_tex;
    QSSGRenderTextureFormat m_format = QSSGRenderTextureFormat::RGBA8;
};

using QSSGAllocateTexturePtr = std::shared_ptr<QSSGAllocateTexture>;

class QSSGColorAttachment : public QSSGCommand
{
public:
    QSSGColorAttachment(const QByteArray &name)
        : QSSGCommand(CommandType::ColorAttachment)
        , m_name(name)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "name:" <<  m_name;
    }

    QSSGRenderTextureFormat format() const
    {
        if (m_textureCmd)
            return m_textureCmd->format();

        return QSSGRenderTextureFormat::Unknown;
    }

    QByteArray m_name;
    QSSGAllocateTexturePtr m_textureCmd;

protected:
    QSSGColorAttachment(const QByteArray &name, CommandType type)
        : QSSGCommand(type)
        , m_name(name)
    {
    }
};

class QSSGDepthTextureAttachment : public QSSGColorAttachment
{
public:
    QSSGDepthTextureAttachment(const QByteArray &name)
        : QSSGColorAttachment(name, CommandType::DepthTextureAttachment)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "name:" <<  m_name;
    }
};

class QSSGDepthStencilAttachment : public QSSGCommand
{
public:
    QSSGDepthStencilAttachment()
        : QSSGCommand(CommandType::DepthStencilAttachment)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "(no parameters)";
    }

    QRhiTexture::Format m_format = QRhiTexture::D24S8;
};

class QSSGAddShaderDefine : public QSSGCommand
{
public:
    QSSGAddShaderDefine()
        : QSSGCommand(CommandType::AddShaderDefine)
        , m_value(0)
    {
    }
    QSSGAddShaderDefine(const QByteArray &name, int value = 0)
        : QSSGCommand(CommandType::AddShaderDefine)
        , m_name(name)
        , m_value(value)
    {
    }
    void addDebug(QDebug &stream) const {
        stream << "define name:" <<  m_name << "value:" << m_value;
    }

    QByteArray m_name;
    int m_value;
};

class QSSGPipelineStateOverrideCommand : public QSSGCommand
{
public:
    std::optional<bool> m_depthTestEnabled;
    std::optional<bool> m_depthWriteEnabled;
    std::optional<bool> m_blendEnabled;
    std::optional<bool> m_usesStencilReference;
    std::optional<bool> m_usesScissor;
    std::optional<QRhiGraphicsPipeline::CompareOp> m_depthFunction;
    std::optional<QRhiGraphicsPipeline::CullMode> m_cullMode;
    std::optional<QRhiGraphicsPipeline::PolygonMode> m_polygonMode;
    std::optional<QRhiGraphicsPipeline::StencilOpState> m_stencilOpFrontState;
    std::optional<quint32> m_stencilWriteMask;
    std::optional<quint32> m_stencilReference;
    std::optional<QRhiViewport> m_viewport;
    std::optional<QRhiScissor> m_scissor;
    std::optional<QRhiGraphicsPipeline::TargetBlend> m_targetBlend0;
    std::optional<QRhiGraphicsPipeline::TargetBlend> m_targetBlend1;
    std::optional<QRhiGraphicsPipeline::TargetBlend> m_targetBlend2;
    std::optional<QRhiGraphicsPipeline::TargetBlend> m_targetBlend3;
    std::optional<QRhiGraphicsPipeline::TargetBlend> m_targetBlend4;
    std::optional<QRhiGraphicsPipeline::TargetBlend> m_targetBlend5;
    std::optional<QRhiGraphicsPipeline::TargetBlend> m_targetBlend6;
    std::optional<QRhiGraphicsPipeline::TargetBlend> m_targetBlend7;
    QSSGPipelineStateOverrideCommand()
        : QSSGCommand(CommandType::PipelineStateOverride)
    {
    }
    void addDebug(QDebug &stream) const {
        // Only print out the ones that exist
        // Elipsis for complex types intentional
        stream << "pipelineState:" << "{";
        if (m_depthTestEnabled.has_value())
            stream << " depthTestEnabled:" << m_depthTestEnabled.value();
        if (m_depthWriteEnabled.has_value())
            stream << " depthWriteEnabled:" << m_depthWriteEnabled.value();
        if (m_blendEnabled.has_value())
            stream << " blendEnabled:" << m_blendEnabled.value();
        if (m_usesStencilReference.has_value())
            stream << " usesStencilReference:" << m_usesStencilReference.value();
        if (m_usesScissor.has_value())
            stream << " usesScissor:" << m_usesScissor.value();
        if (m_depthFunction.has_value())
            stream << " depthFunction:" << int(m_depthFunction.value());
        if (m_cullMode.has_value())
            stream << " cullMode:" << int(m_cullMode.value());
        if (m_polygonMode.has_value())
            stream << " polygonMode:" << int(m_polygonMode.value());
        if (m_stencilOpFrontState.has_value())
            stream << " stencilOpFrontState:" << "{...}";
        if (m_stencilWriteMask.has_value())
            stream << " stencilWriteMask:" << m_stencilWriteMask.value();
        if (m_stencilReference.has_value())
            stream << " stencilReference:" << m_stencilReference.value();
        if (m_viewport.has_value())
            stream << " viewport:" << "{...}";
        if (m_scissor.has_value())
            stream << " scissor:" << "{...}";
        if (m_targetBlend0.has_value())
            stream << " targetBlend0:" << "{...}";
        if (m_targetBlend1.has_value())
            stream << " targetBlend1:" << "{...}";
        if (m_targetBlend2.has_value())
            stream << " targetBlend2:" << "{...}";
        if (m_targetBlend3.has_value())
            stream << " targetBlend3:" << "{...}";
        if (m_targetBlend4.has_value())
            stream << " targetBlend4:" << "{...}";
        if (m_targetBlend5.has_value())
            stream << " targetBlend5:" << "{...}";
        if (m_targetBlend6.has_value())
            stream << " targetBlend6:" << "{...}";
        if (m_targetBlend7.has_value())
            stream << " targetBlend7:" << "{...}";
        stream << " }";
    }
};

QT_END_NAMESPACE

#endif
